#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sndio.h>

#include "au.h"
#include "adev.h"
#include "common.h"

enum {
	A_NULL,
	A_ENC,
	A_RATE,
	A_CHAN
};

struct adev {
	struct sio_hdl *sio;
	int needstart;
};

const struct a_lookup stab[] = {
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

struct adev *
ad_open(const char *dev, int mode)
{
	struct adev *a;

	if ((a = malloc(sizeof(struct adev))) == NULL)
		return (NULL);
	if ((a->sio = sio_open(dev != NULL ? dev : SIO_DEVANY,
	    (mode & A_WRITE) ? SIO_PLAY : SIO_REC, 0)) == NULL) {
		free(a);
		return (NULL);
	}
	a->needstart = 1;
	return (a);
}

struct adev *
ad_fpopen(FILE *fp, int mode)
{

	ERR(EOPNOTSUPP, NULL);
}

void
ad_close(struct adev *a)
{

	if (a == NULL)
		return;
	if (!a->needstart)
		(void)sio_stop(a->sio);
	sio_close(a->sio);
	free(a);
}

int
ad_limit(struct adev *a)
{
#ifdef USE_PLEDGE

	return (pledge("audio stdio", NULL));
#else

	ERR(ENOSYS, -1);
#endif
}

int
ad_get(struct adev *a, const char *s, ...)
{

	ERR(EOPNOTSUPP, -1);
}

int
ad_set(struct adev *a, const char *s, ...)
{
	struct sio_par p;
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (!a->needstart && !sio_stop(a->sio))
		ERR(EOPNOTSUPP, -1);
	a->needstart = 1;
	sio_initpar(&p);
	p.le = 0;
	p.sig = 1;
	p.msb = 1;
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_ENC:
			switch (va_arg(ap, uint32_t)) {
			case AU_PCM8:
				p.bits = 8;
				break;
			case AU_PCM16:
				p.bits = 16;
				break;
			case AU_PCM24:
				p.bits = 24;
				break;
			case AU_PCM32:
				p.bits = 32;
				break;
			default:
				ERR(EINVAL, -1);
			}
			p.bps = SIO_BPS(p.bits);
			break;
		case A_RATE:
			p.rate = va_arg(ap, uint32_t);
			break;
		case A_CHAN:
			p.rchan = p.pchan = va_arg(ap, uint32_t);
			break;
		default:
			goto err;
		}
	if (r == 0) {
		if (!sio_setpar(a->sio, &p))
			goto err;
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}


ssize_t
ad_write(struct adev *a, const void *b, size_t size)
{
	size_t r;

	if (a == NULL || b == NULL)
		ERR(EFAULT, -1);
	if (a->needstart) {
		if (!sio_start(a->sio))
			ERR(EINVAL, -1);
		a->needstart = 0;
	}
	while ((r = sio_write(a->sio, b, size)) == 0 &&
	    !sio_eof(a->sio))
		/* do nothing */;
	return (r);
}

ssize_t
ad_read(struct adev *a, void *b, size_t size)
{
	size_t r;

	if (a == NULL || b == NULL)
		ERR(EFAULT, -1);
	if (a->needstart) {
		if (!sio_start(a->sio))
			ERR(EINVAL, -1);
		a->needstart = 0;
	}
	while ((r = sio_read(a->sio, b, size)) == 0 && !sio_eof(a->sio))
		/* do nothing */;
	return (r);
}
